﻿using System;
using System.Collections.Generic;
using MongoDB.Bson;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.IdGenerators;

namespace Yamfinder.Data
{
    public class LexicalEntrySerialiser : IBsonSerializer
    {
        private readonly IDataDictonaryRepository _dataDictonaryRepository;

        public LexicalEntrySerialiser(IDataDictonaryRepository dataDictonaryRepository)
        {
            _dataDictonaryRepository = dataDictonaryRepository;
        }

        public object Deserialize(BsonReader bsonReader, Type nominalType, IBsonSerializationOptions options)
        {
            if (nominalType != typeof(LexicalEntry)) throw new ArgumentException("Cannot deserialize anything but LexicalEntry");

            var lexicalEntry = new LexicalEntry {Fields = new Dictionary<string, object>()};
            var document = BsonDocument.ReadFrom(bsonReader);

            foreach (var name in document.Names)
            {
                //picks up the id
                if (name == "_id")
                {
                    lexicalEntry.Id =  document[name].AsObjectId;

                    continue;
                }

                var type = _dataDictonaryRepository.Get(name).Type;
                switch (type)
                {
                    case "GPSLocation":
                        var val = document[name].AsBsonDocument;
                        var lat = val.GetElement("lat").Value.AsDouble;
                        var lon = val.GetElement("lon").Value.AsDouble;
                        lexicalEntry.Fields.Add(name, new GPSLoc() {Lat = lat, Lon = lon});
                        break;

                    case "RelativeFileLoc":
                    case "Text":
                        lexicalEntry.Fields.Add(name, document[name].AsString);
                        break;

                    case "Integer":
                        lexicalEntry.Fields.Add(name, document[name].AsInt64);
                        break;

                    case "Decimal":
                        lexicalEntry.Fields.Add(name, document[name].AsDouble);
                        break;

                    case "Date":
                        lexicalEntry.Fields.Add(name, document[name].AsDateTime);
                        break;

                    default:
                        throw new ArgumentException("Error during deserialisation, no such field type! Name: " + name + " Type: " + type);
                } 
            }

            return lexicalEntry;
        }

        public object Deserialize(BsonReader bsonReader, Type nominalType, Type actualType, IBsonSerializationOptions options)
        {
            throw new NotImplementedException();
        }

        public IBsonSerializationOptions GetDefaultSerializationOptions()
        {
            throw new NotImplementedException();
        }

        public bool GetDocumentId(object document, out object id, out Type idNominalType, out IIdGenerator idGenerator)
        {
            var lexicalEntry = document as LexicalEntry;

            if (lexicalEntry == null) throw new ArgumentException("document is not of the LexicalEntry type and cannot be deserialised.", "document");
            
            id = lexicalEntry.Id;
            idNominalType = typeof (ObjectId);
            idGenerator = new ObjectIdGenerator();
            return true;
        }

        public BsonSerializationInfo GetItemSerializationInfo()
        {
            throw new NotImplementedException();
        }

        public BsonSerializationInfo GetMemberSerializationInfo(string memberName)
        {
            throw new NotImplementedException();
        }

        public void Serialize(BsonWriter bsonWriter, Type nominalType, object value, IBsonSerializationOptions options)
        {
            if (nominalType != typeof(LexicalEntry)) throw new ArgumentException("Cannot serialize anything but LexicalEntry");

            var lexicalEntry = (LexicalEntry) value;

            bsonWriter.WriteStartDocument();

            foreach (var field in lexicalEntry.Fields)
            {
                var type = _dataDictonaryRepository.Get(field.Key).Type;
                switch (type)
                {
                    case "GPSLocation":
                        if (string.IsNullOrEmpty(field.Value.ToString())) break;
                        var gpsLoc = (GPSLoc) field.Value;
                        bsonWriter.WriteStartDocument(field.Key);
                        bsonWriter.WriteDouble("lat", gpsLoc.Lat);
                        bsonWriter.WriteDouble("lon", gpsLoc.Lon);
                        bsonWriter.WriteEndDocument();
                        break;

                    case "RelativeFileLoc":
                    case "Text":
                        bsonWriter.WriteString(field.Key,(string)field.Value);
                        break;

                    case "Integer":
                        if (string.IsNullOrEmpty(field.Value.ToString())) break;
                        bsonWriter.WriteInt64(field.Key, Convert.ToInt64(field.Value));
                        break;

                    case "Decimal":
                        if (string.IsNullOrEmpty(field.Value.ToString())) break;
                        bsonWriter.WriteDouble(field.Key, Convert.ToDouble(field.Value));
                        break;

                    case "Date":
                        if (string.IsNullOrEmpty(field.Value.ToString())) break;
                        bsonWriter.WriteDateTime(field.Key, Convert.ToDateTime(field.Value).ToUnixEpochTime());
                        break;

                    default:
                        throw new ArgumentException("Error during serialisation, no such field type! Name: " + field.Key + " Type: " + type);
                } 
            }

            bsonWriter.WriteEndDocument();
        }

        public void SetDocumentId(object document, object id)
        {
            var lexicalEntry = document as LexicalEntry;

            if (lexicalEntry == null) throw new ArgumentException("document is not of the LexicalEntry type and cannot be serialised.","document");

            lexicalEntry.Id = (ObjectId) id;
        }
    }
}